package cn.lg.soar.common.algorithm.serial;

import cn.lg.soar.common.util.IpUtil;
import cn.lg.soar.common.util.data.DataUtil;
import cn.lg.soar.common.util.hardware.SystemUtils;

import java.text.DecimalFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;

/**
 * 序列号生成器
 * 共25位十进制，前8位是日期，后17是由52位二进制生成(52位二进制格式：27位当日毫秒值，12位机器码，13位流水号)
 * @author luguoxiang
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
public class Sequence {

    private final static Sequence SEQUENCE = new Sequence();
    private final static DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyyMMdd");

    /**
     * 配置数据（初始化后不在变化）
     */
    private final DecimalFormat SERIAL_FORMAT = new DecimalFormat("00000000000000000");
    private final int characteristic; // 12位机器码
    private final int secret; //混淆 [0 - 13600000),默认值：19970416


    /**
     * 运行数据（运行中不断改变）
     */
    private volatile String date;
    private long timestamp = 0;
    private long serial = 0;
    private long serialMax = 0;

    public Sequence() {
        this(19970416);
    }

    public Sequence(int secret) {
        this.characteristic = (getHostCharacteristic() & 4095) << 13;
        this.secret = secret % 13600000;
    }


    /**
     * 获取下一个id
     * @return
     */
    public synchronized String next(){
        long ts = System.currentTimeMillis();
        if (ts != this.timestamp) {
            // 生成下一毫秒的数据
            LocalDateTime now = LocalDateTime.now();
            long prefix = now.toLocalTime().get(ChronoField.MILLI_OF_DAY) + secret;
            prefix = (prefix << 25) | characteristic;
            this.date = now.toLocalDate().format(DATE_FORMAT);
            this.serial = prefix;
            this.serialMax = prefix + 8192;
            this.timestamp = ts;
        }
        if (this.serial == this.serialMax) {
            throw new RuntimeException("Serial number out of range，Up to 8192 can be generated in one millisecond");
        }
        return this.date + SERIAL_FORMAT.format(this.serial++);
    }

    public static String nextValue(){
        return SEQUENCE.next();
    }

    /**
     * 获取主机标识
     * @return
     */
    private static int getHostCharacteristic() {
        String processorId = SystemUtils.getProcessorId();
        if (processorId == null) {
            return IpUtil.ip4vToInt(IpUtil.getLocalIp());
        }
        byte[] bytes = DataUtil.hexToBytes(processorId);
        long l = DataUtil.byteToLong(bytes);
        return (int)(l & 0x3FFFFF);
    }

}
